---
title: Content Collections
description: >-
  Content collections help organize your Markdown and type-check your
  frontmatter with schemas.
i18nReady: true
---
import { FileTree } from '@astrojs/starlight/components';
import Since from '~/components/Since.astro'
import RecipeLinks from "~/components/RecipeLinks.astro"
import Badge from "~/components/Badge.astro"


<p><Since v="2.0.0" /></p>

**Content collections** are the best way to manage and author content in any Astro project. Collections help to organize your documents, validate your frontmatter, and provide automatic TypeScript type-safety for all of your content.

## What are Content Collections?

A **content collection** is any top-level directory inside the reserved `src/content` project directory, such as `src/content/newsletter` and `src/content/authors`. Only content collections are allowed inside the `src/content` directory. This directory cannot be used for anything else.

A **collection entry** is any piece of content stored inside of your content collection directory. Entries can use content authoring formats including Markdown (`.md`) and MDX (`.mdx` using the [MDX integration](/en/guides/integrations-guide/mdx/)) or as one of two supported data formats: YAML (`.yaml`) and JSON (`.json`). We recommend using a consistent naming scheme (lower-case, dashes instead of spaces) for your files to make it easier to find and organize your content, but this is not required. You can also [exclude entries from being built](/en/guides/routing/#excluding-pages) by prefixing the filename with an underscore (_).

<FileTree>
- src/content/
  - **newsletter/** the "newsletter" collection
    - week-1.md a collection entry
    - week-2.md a collection entry
    - week-3.md a collection entry
</FileTree>

Once you have a collection, you can start [querying your content](#querying-collections) using Astro's built-in content APIs.

### The ".astro" Directory

Astro stores important metadata for content collections in an `.astro` directory in your project. No action is needed on your part to maintain or update this directory. You are encouraged to ignore it entirely while working in your project.

The `.astro` directory will be updated for you automatically anytime you run the [`astro dev`](/en/reference/cli-reference/#astro-dev), [`astro build`](/en/reference/cli-reference/#astro-build) commands. You can run [`astro sync`](/en/reference/cli-reference/#astro-sync) at any time to update the `.astro` directory manually.

:::tip
If you're using Git for version control, we recommend ignoring the `.astro` directory by adding `.astro` to your `.gitignore`. This tells Git to ignore this directory and any files inside of it.

```bash
echo "\n.astro" >> .gitignore
```
:::



### Organizing with multiple collections 

If two files represent different kinds of content (e.g. a blog post and an author profile), they most likely belong in different collections. This is important because many features (frontmatter validation, automatic TypeScript type-safety) require that all entries in a collection share a similar structure.

If you find yourself working with different types of content, you should create multiple collections to represent each type. You can create as many different collections in your project as you'd like.

<FileTree>
- src/content/
  - **newsletter/** 
    - week-1.md
    - week-2.md
  - **blog/**
    - post-1.md
    - post-2.md
  - **authors/**
    - grace-hopper.json
    - alan-turing.json
</FileTree>


### Organizing with subdirectories

A content collection is always a top-level folder inside of the `src/content/` directory. You cannot nest one collection inside of another. However, you can use subdirectories to organize your content within a collection.

For example, you can use the following directory structure to organize i18n translations within a single `docs` collection. When you query this collection, you'll be able to filter the result by language using the file path. 

<FileTree>
- src/content/
  - docs/ this collection uses subdirectories to organize by language
    - **en/**
    - **es/**
    - **de/**
</FileTree>

## Defining Collections

:::note
The `src/content/config.ts` file is optional. However, choosing not to define your collections will disable some of their best features like frontmatter schema validation or automatic TypeScript typings. 
:::

To get the most out of your content collections, create a `src/content/config.ts` file in your project (`.js` and `.mjs` extensions are also supported.) This is a special file that Astro will automatically load and use to configure your content collections.


```ts
// src/content/config.ts
// 1. Import utilities from `astro:content`
import { defineCollection } from 'astro:content';
// 2. Define your collection(s)
const blogCollection = defineCollection({ /* ... */ });
// 3. Export a single `collections` object to register your collection(s)
//    This key should match your collection directory name in "src/content"
export const collections = {
  'blog': blogCollection,
};
```


### Setting up TypeScript

If you **do not** already extend Astro's `strict` or `strictest` recommended TypeScript settings in your `tsconfig.json` file, you may need to update your `tsconfig.json` to enable `strictNullChecks`.

```json title="tsconfig.json" ins={5}
{
  // Note: No change needed if you use "astro/tsconfigs/strict" or "astro/tsconfigs/strictest"
  "extends": "astro/tsconfigs/base",
  "compilerOptions": {
    "strictNullChecks": true
  }
}
```

If you use `.js` or `.mjs` files in an Astro project, you can enable IntelliSense and type checking in your editor by enabling `allowJs` in your `tsconfig.json`:
```json title="tsconfig.json" ins={6}
{
  // Note: No change needed if you use "astro/tsconfigs/strict" or "astro/tsconfigs/strictest"
  "extends": "astro/tsconfigs/base",
  "compilerOptions": {
    "strictNullChecks": true,
    "allowJs": true
  }
}
```

### Defining a collection schema

Schemas enforce consistent frontmatter or entry data within a collection. A schema **guarantees** that this data exists in a predictable form when you need to reference or query it. If any file violates its collection schema, Astro will provide a helpful error to let you know.

Schemas also power Astro's automatic TypeScript typings for your content. When you define a schema for your collection, Astro will automatically generate and apply a TypeScript interface to it. The result is full TypeScript support when you query your collection, including property autocompletion and type-checking. 

To define your first collection, create a `src/content/config.ts` file if one does not already exist (`.js` and `.mjs` extensions are also supported.) This file should:

1. **Import the proper utilities** from `astro:content`. 
2. **Define each collection that you'd like to validate.** This includes a `type` (introduced in Astro v2.5.0) specifying whether the collection contains content authoring formats like Markdown (`type: 'content'`) or data formats like JSON or YAML (`type: 'data'`). It also includes a `schema` that defines the shape of your frontmatter or entry data.
3. **Export a single `collections` object** to register your collections.

```ts
// src/content/config.ts
// 1. Import utilities from `astro:content`
import { z, defineCollection } from 'astro:content';

// 2. Define a `type` and `schema` for each collection
const blogCollection = defineCollection({
  type: 'content', // v2.5.0 and later
  schema: z.object({
    title: z.string(),
    tags: z.array(z.string()),
    image: z.string().optional(),
  }),
});

// 3. Export a single `collections` object to register your collection(s)
export const collections = {
  'blog': blogCollection,
};
```

### Defining multiple collections

You can use `defineCollection()` as many times as you want to create multiple schemas.  All collections must be exported from inside the single `collections` object.

```ts
// src/content/config.ts
const blogCollection = defineCollection({
  type: 'content',
  schema: z.object({ /* ... */ })
});
const newsletter = defineCollection({
  type: 'content',
  schema: z.object({ /* ... */ })
});
const authors = defineCollection({
  type: 'data',
  schema: z.object({ /* ... */ })
});

export const collections = {
  'blog': blogCollection,
  'newsletter': newsletter,
  'authors': authors,
};
```

As your project grows, you are also free to reorganize your codebase and move logic out of the `src/content/config.ts` file. Defining your schemas separately can be useful for reusing schemas across multiple collections and sharing schemas with other parts of your project.

```ts
// src/content/config.ts
// 1. Import your utilities and schemas
import { defineCollection } from 'astro:content';
import { blogSchema, authorSchema } from '../schemas';

// 2. Define your collections
const blogCollection = defineCollection({
  type: 'content',
  schema: blogSchema,
});
const authorCollection = defineCollection({
  type: 'data',
  schema: authorSchema,
});

// 3. Export multiple collections to register them
export const collections = {
  'blog': blogCollection,
  'authors': authorCollection,
};
```

### Using third-party collection schemas

You can import collection schemas from anywhere, including external npm packages. This can be useful when working with themes and libraries that provide their own collection schemas for you to use.


```ts
// src/content/config.ts
import { blogSchema } from 'my-blog-theme';
const blogCollection = defineCollection({ type: 'content', schema: blogSchema });

// Export the blog collection, using an external schema from 'my-blog-theme'
export const collections = {
  'blog': blogCollection,
};
```


### Defining datatypes with Zod

Astro uses [Zod](https://github.com/colinhacks/zod) to power its content schemas. With Zod, Astro is able to validate every file's frontmatter within a collection *and* provide automatic TypeScript types when you go to query content from inside your project.

To use Zod in Astro, import the `z` utility from `"astro:content"`. This is a re-export of the Zod library, and it supports all of the features of Zod. See [Zod’s README](https://github.com/colinhacks/zod) for complete documentation on how Zod works and what features are available.


```ts
// Example: A cheatsheet of many common Zod datatypes
import { z, defineCollection } from 'astro:content';

defineCollection({
  schema: z.object({
    isDraft: z.boolean(),
    title: z.string(),
    sortOrder: z.number(),
    image: z.object({
      src: z.string(),
      alt: z.string(),
    }),
    author: z.string().default('Anonymous'),
    language: z.enum(['en', 'es']),
    tags: z.array(z.string()),
    // An optional frontmatter property. Very common!
    footnote: z.string().optional(),
    // In frontmatter, dates written without quotes around them are interpreted as Date objects
    publishDate: z.date(),
    // You can also transform a date string (e.g. "2022-07-08") to a Date object
    // publishDate: z.string().transform((str) => new Date(str)),
    // Advanced: Validate that the string is also an email
    authorContact: z.string().email(),
    // Advanced: Validate that the string is also a URL
    canonicalURL: z.string().url(),
  })
})
```

### Defining collection references

Collection entries can also "reference" other related entries. 

With the `reference()` function from the Collections API, you can define a property in a collection schema as an entry from another collection. For example, you can require that every `space-shuttle` entry includes a `pilot` property which uses the `pilot` collection's own schema for type checking, autocomplete, and validation.

A common example is a blog post that references reusable author profiles stored as JSON, or related post URLs stored in the same collection:

```ts
import { defineCollection, reference, z } from 'astro:content';

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    // Reference a single author from the `authors` collection by `id`
    author: reference('authors'),
    // Reference an array of related posts from the `blog` collection by `slug`
    relatedPosts: z.array(reference('blog')),
  })
});

const authors = defineCollection({
  type: 'data',
  schema: z.object({
    name: z.string(),
    portfolio: z.string().url(),
  })
});

export const collections = { blog, authors };
```

This example blog post specifies the `slug`s of related posts and the `id` of the post author:

```yaml title="src/content/blog/welcome.md"
---
title: "Welcome to my blog"
author: ben-holmes # references `src/content/authors/ben-holmes.json`
relatedPosts:
- about-me # references `src/content/blog/about-me.md`
- my-year-in-review # references `src/content/blog/my-year-in-review.md`
---
```

### Defining custom slugs

When using `type: 'content'`, every content entry generates a URL-friendly `slug` property from its [file `id`](/en/reference/api-reference/#id). The slug is used to query the entry directly from your collection. It is also useful when creating new pages and URLs from your content.

You can override an entry's generated slug by adding your own `slug` property to the file frontmatter. This is similar to the "permalink" feature of other web frameworks. `"slug"` is a special, reserved property name that is not allowed in your custom collection `schema` and will not appear in your entry's `data` property. 

```md {3}
---
title: My Blog Post
slug: my-custom-slug/supports/slashes
---
Your blog post content here.
```

## Querying Collections

Astro provides two functions to query a collection and return one (or more) content entries: [`getCollection()`](/en/reference/api-reference/#getcollection) and [`getEntry()`](/en/reference/api-reference/#getentry).

```js
import { getCollection, getEntry } from 'astro:content';

// Get all entries from a collection.
// Requires the name of the collection as an argument.
// Example: retrieve `src/content/blog/**`
const allBlogPosts = await getCollection('blog');

// Get a single entry from a collection.
// Requires the name of the collection and either
// the entry `slug` (content collections) or `id` (data collections)
// Example: retrieve `src/content/authors/grace-hopper.json`
const graceHopperProfile = await getEntry('authors', 'grace-hopper');
```

Both functions return content entries as defined by the [`CollectionEntry`](/en/reference/api-reference/#collection-entry-type) type.

### Accessing referenced data

Any [references defined in your schema](#defining-collection-references) must be queried separately after first querying your collection entry. You can use the `getEntry()` function again, or `getEntries()`, to retrieve the referenced entry from the returned `data` object. 

```astro title="src/pages/blog/welcome.astro"
---
import { getEntry, getEntries } from 'astro:content';

const blogPost = await getEntry('blog', 'welcome');

// Resolve a singular reference
const author = await getEntry(blogPost.data.author);
// Resolve an array of references
const relatedPosts = await getEntries(blogPost.data.relatedPosts);
---

<h1>{blogPost.data.title}</h1>
<p>Author: {author.data.name}</p>

<!-- ... -->

<h2>You might also like:</h2>
{relatedPosts.map(p => (
  <a href={p.slug}>{p.data.title}</a>
))}
```

### Filtering collection queries

`getCollection()` takes an optional "filter" callback that allows you to filter your query based on an entry's `id` or `data` (frontmatter) properties. For collections of `type: 'content'`, you can also filter based on `slug`.

:::note
The `slug` property is specific to content collections, and will not be available when filtering collections of JSON or YAML.
:::

You can use this to filter by any content criteria you like. For example, you can filter by properties like `draft` to prevent any draft blog posts from publishing to your blog:

```js
// Example: Filter out content entries with `draft: true`
import { getCollection } from 'astro:content';
const publishedBlogEntries = await getCollection('blog', ({ data }) => {
  return data.draft !== true;
});
```

You can also create draft pages that are available when running the dev server, but not built in production:

```js
// Example: Filter out content entries with `draft: true` only when building for production
import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog', ({ data }) => {
  return import.meta.env.PROD ? data.draft !== true : true;
});
```

The filter argument also supports filtering by nested directories within a collection. Since the `id` includes the full nested path, you can filter by the start of each `id` to only return items from a specific nested directory:

```js
// Example: Filter entries by sub-directory in the collection
import { getCollection } from 'astro:content';
const englishDocsEntries = await getCollection('docs', ({ id }) => {
  return id.startsWith('en/');
});
```

### Using content in Astro templates

Once you have queried your collection entries, you can access each entry directly inside of your Astro component template. This lets you to render HTML for things like links to your content (using the content `slug`) or information about your content (using the `data` property).

For information about rendering your content to HTML, see [Rendering Content to HTML](#rendering-content-to-html) below.

```astro
---
// src/pages/index.astro
import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog');
---
<ul>
  {blogEntries.map(blogPostEntry => (
    <li>
      <a href={`/my-blog-url/${blogPostEntry.slug}`}>{blogPostEntry.data.title}</a>
      <time datetime={blogPostEntry.data.publishedDate.toISOString()}>
        {blogPostEntry.data.publishedDate.toDateString()}
      </time>
    </li>
  ))}
</ul>
```

### Passing content as props

A component can also pass an entire content entry as a prop. 

If you do this, you can use the [`CollectionEntry`](/en/reference/api-reference/#collection-entry-type) utility to correctly type your components props using TypeScript.  This utility takes a string argument that matches the name of your collection schema, and will inherit all of the properties of that collection's schema.

```astro /CollectionEntry(?:<.+>)?/
---
// src/components/BlogCard.astro
import type { CollectionEntry } from 'astro:content';
interface Props {
  post: CollectionEntry<'blog'>;
}

// `post` will match your 'blog' collection schema type
const { post } = Astro.props;
---
```

### Rendering content to HTML

Once queried, you can render Markdown and MDX entries to HTML using the entry `render()` function property. Calling this function gives you access to rendered content and metadata, including both a `<Content />` component and a list of all rendered headings.

```astro {5}
---
// src/pages/render-example.astro
import { getEntry } from 'astro:content';
const entry = await getEntry('blog', 'post-1');
const { Content, headings } = await entry.render();
---
<p>Published on: {entry.data.published.toDateString()}</p>
<Content />
```


## Generating Routes from Content

Content collections are stored outside of the `src/pages/` directory. This means that no routes are generated for your collection items by default. You will need to manually create a new [dynamic route](/en/guides/routing/#dynamic-routes) to generate HTML pages from your collection entries. Your dynamic route will map the incoming request param (ex: `Astro.params.slug` in `src/pages/blog/[...slug].astro`) to fetch the correct entry inside a collection.

The exact method for generating routes will depend on your build [`output`](/en/reference/configuration-reference/#output) mode: 'static' (the default) or 'server' (for SSR).

### Building for static output (default)

If you are building a static website (Astro's default behavior), you would use the [`getStaticPaths()`](/en/reference/api-reference/#getstaticpaths) function to create multiple pages from a single `src/pages/` component during your build.

Call [`getCollection()`](/en/reference/api-reference/#getcollection) inside of `getStaticPaths()` to query your content. Then, create your new URL paths using the `slug` property of each content entry.

```astro "{ slug: entry.slug }"
---
// src/pages/posts/[...slug].astro
import { getCollection } from 'astro:content';
// 1. Generate a new path for every collection entry
export async function getStaticPaths() {
  const blogEntries = await getCollection('blog');
  return blogEntries.map(entry => ({
    params: { slug: entry.slug }, props: { entry },
  }));
}
// 2. For your template, you can get the entry directly from the prop
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<h1>{entry.data.title}</h1>
<Content />
```

This will generate a new page for every entry in the `blog` collection. For example, an entry at `src/content/blog/hello-world.md` will have a slug of `hello-world`, and therefore its final URL will be `/posts/hello-world/`.

:::note
If your custom slugs contain the `/` character to produce URLs with multiple path segments, you must use a [rest parameter (`[...slug]`)](/en/guides/routing/#rest-parameters) in the `.astro` filename for this dynamic routing page.
:::

### Building for server output (SSR)

If you are building a dynamic website (using Astro's SSR support), you are not expected to generate any paths ahead of time during the build. Instead, your page should examine the request (using `Astro.request` or `Astro.params`) to find the `slug` on-demand, and then fetch it using [`getEntry()`](/en/reference/api-reference/#getentry).


```astro
---
// src/pages/posts/[...slug].astro
import { getEntry } from "astro:content";
// 1. Get the slug from the incoming server request
const { slug } = Astro.params;
if (slug === undefined) {
	throw new Error("Slug is required");
}
// 2. Query for the entry directly using the request slug
const entry = await getEntry("blog", slug);
// 3. Redirect if the entry does not exist
if (entry === undefined) {
	return Astro.redirect("/404");
}
// 4. (Optional) Render the entry to HTML in the template
const { Content } = await entry.render();
---
```

## Migrating from File-Based Routing

If you have an existing Astro project, such as a blog, that uses Markdown or MDX files in subfolders inside `src/pages/`, consider migrating related content or data files to content collections. 
 
See how to convert a basic blog example from `src/pages/posts/` to `src/content/posts` in our [step-by-step tutorial](/en/tutorials/add-content-collections/) that uses the codebase from [the Build a Blog tutorial's finished project](https://github.com/withastro/blog-tutorial-demo).

## Enabling Build Caching

<p><Since v="3.5.0" /><Badge>Experimental</Badge></p>

If you are working with large collections, you may wish to enable cached builds with the [`experimental.contentCollectionCache`](/en/reference/configuration-reference/#experimentalcontentcollectioncache) flag. This experimental feature optimizes Astro's build process, enabling unchanged collections to be stored and reused between builds.

In many cases, this can lead to significant build performance improvements.

While this feature stabilizes, you may run into issues with the stored cache. You can always reset your build cache by running the following command:

```
npm run astro build -- --force
```


## Modifying Frontmatter with Remark

:::caution
**Not recommended.** Remark and rehype plugins access the _raw_ Markdown or MDX document frontmatter. This means that `remarkPluginFrontmatter` frontmatter is handled separately from your type-safe `schema`, and will not reflect any changes or defaults applied through Astro. Use at your own risk!
:::

Astro supports remark or rehype plugins that [modify your frontmatter directly](/en/guides/markdown-content/#modifying-frontmatter-programmatically). You can access this modified frontmatter inside of a content entry by using the `remarkPluginFrontmatter` property returned from `render()`:

```astro "{ remarkPluginFrontmatter }"
---
import { getEntry } from 'astro:content';
const blogPost = await getEntry('blog', 'post-1');
const { remarkPluginFrontmatter } = await blogPost.render();
---
<p>{blogPost.data.title} — {remarkPluginFrontmatter.readingTime}</p>
```

<RecipeLinks slugs={["en/recipes/reading-time" ]}/>

The remark and rehype pipelines only run when your content is rendered, which explains why `remarkPluginFrontmatter` is only available after you call `render()` on your content entry. In contrast, `getCollection()` and `getEntry()` cannot return these values directly because they do not render your content.

## Working with dates in the frontmatter

Several date formats are possible in content collections, but your collection's schema must match the format used in your Markdown or MDX YAML frontmatter.

YAML uses the [ISO-8601](https://www.iso.org/iso-8601-date-and-time-format.html) standard to express dates. Use the format `yyyy-mm-dd` (e.g. `2021-07-28`) along with a schema type of `z.date()`:

```markdown title="src/pages/posts/example-post.md"
---
title: My Blog Post
pubDate: 2021-07-08
---
```

The date format will be specified in UTC if a timezone is not provided. If you need to specify a timezone, you can use the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format. 

```markdown title="src/pages/posts/example-post.md"
---
title: My Blog Post
pubDate: 2021-07-08T12:00:00-04:00
---
```

To render only the `YYYY-MM-DD` from the full UTC timestamp, use the JavaScript `slice` method to remove the timestamp:

```astro title="src/layouts/ExampleLayout.astro"
---
const { frontmatter } = Astro.props;
---
<h1>{frontmatter.title}</h1>
<p>{frontmatter.pubDate.toString().slice(0,10)}</p>
```
To see an example of using `toLocaleDateString` to format the day, month, and year instead, see the [`<FormattedDate />` component](https://github.com/withastro/astro/blob/latest/examples/blog/src/components/FormattedDate.astro) in the official Astro blog template. 



